package com.neo.tiny.bpm.service.task.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.neo.tiny.admin.api.dept.DeptApi;
import com.neo.tiny.admin.api.user.AdminUserApi;
import com.neo.tiny.admin.dto.dept.SysDeptDTO;
import com.neo.tiny.admin.dto.user.UserInfoDTO;
import com.neo.tiny.bpm.entity.BpmTaskExtDO;
import com.neo.tiny.bpm.enums.task.BpmProcessInstanceResultEnum;
import com.neo.tiny.bpm.mapper.BpmTaskExtMapper;
import com.neo.tiny.bpm.service.task.BpmProcessInstanceExtService;
import com.neo.tiny.bpm.service.task.BpmTaskService;
import com.neo.tiny.bpm.vo.task.*;
import com.neo.tiny.common.constant.ErrorCodeConstants;
import com.neo.tiny.common.exception.WebApiException;
import com.neo.tiny.common.util.NumberUtils;
import com.neo.tiny.query.LambdaQueryWrapperBase;
import com.neo.tiny.util.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Author: yqz
 * @CreateDate: 2022/10/7 22:46
 */
@Slf4j
@Service
public class BpmTaskServiceImpl implements BpmTaskService {

    @Resource
    private TaskService taskService;

    @Resource
    private HistoryService historyService;

    @Resource
    private BpmTaskExtMapper taskExtMapper;

    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private DeptApi deptApi;

    @Lazy
    @Resource
    private BpmProcessInstanceExtService processInstanceExtService;

    @Resource
    private RepositoryService repositoryService;

    @Override
    public List<Task> getTasksByProcessInstanceIds(Collection<String> processInstanceIds) {
        if (CollUtil.isEmpty(processInstanceIds)) {
            return Collections.emptyList();
        }
        return taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).list();
    }

    @Override
    public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {

        // 获得任务列表
        List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime()
                // 创建时间倒序
                .desc()
                .list();
        if (CollUtil.isEmpty(tasks)) {
            return Collections.emptyList();
        }

        Set<String> taskIds = tasks.stream()
                .map(HistoricTaskInstance::getId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        List<BpmTaskExtDO> bpmTaskExtList = taskExtMapper.selectList(new LambdaQueryWrapperBase<BpmTaskExtDO>()
                .in(BpmTaskExtDO::getTaskId, taskIds));

        // 获得 TaskExtDO Map
        Map<String, BpmTaskExtDO> taskMap = bpmTaskExtList.stream()
                .collect(Collectors.toMap(BpmTaskExtDO::getTaskId, Function.identity(), (key1, key2) -> key1));

        HistoricProcessInstance processInstance = getHistoricProcessInstance(processInstanceId);
        // 获得 User Map
        Set<Long> userIds = tasks.stream().map(task -> NumberUtils.parseUpperCaseLong(task.getAssignee()))
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        userIds.add(NumberUtils.parseUpperCaseLong(processInstance.getStartUserId()));

        Map<Long, UserInfoDTO> userMap = adminUserApi.getUserMap(userIds);
        // 获得 Dept Map
        Set<Long> deptIds = userMap.values().stream()
                .map(UserInfoDTO::getDeptId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, SysDeptDTO> deptMap = deptApi.getDeptMap(deptIds);

        // 拼接数据
        return tasks.stream().map(task -> {
            BpmTaskRespVO vo = new BpmTaskRespVO();
            BeanUtil.copyProperties(task, vo);
            BpmTaskExtDO taskExtDO = taskMap.get(task.getId());
            if (BeanUtil.isNotEmpty(taskExtDO)) {
                BeanUtil.copyProperties(taskExtDO, vo, "id");
            }
            if (BeanUtil.isNotEmpty(processInstance)) {
                UserInfoDTO userInfoDTO = userMap.get(NumberUtils.parseUpperCaseLong(processInstance.getStartUserId()));

                BpmTaskTodoPageItemRespVO.ProcessInstance instance = new BpmTaskTodoPageItemRespVO.ProcessInstance();

                instance.setId(processInstance.getId());
                instance.setName(processInstance.getName());
                instance.setStartUserId(NumberUtils.parseUpperCaseLong(processInstance.getStartUserId()));
                instance.setProcessDefinitionId(processInstance.getProcessDefinitionId());
                instance.setStartUserNickname(userInfoDTO.getNickName());
                vo.setProcessInstance(instance);
            }
            UserInfoDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
            if (assignUser != null) {
                BpmTaskRespVO.User user = new BpmTaskRespVO.User();
                user.setId(assignUser.getId());
                user.setNickname(assignUser.getNickName());
                user.setDeptId(assignUser.getDeptId());
                vo.setAssigneeUser(user);

                SysDeptDTO dept = deptMap.get(assignUser.getDeptId());
                if (dept != null) {
                    vo.getAssigneeUser().setDeptName(dept.getDeptName());
                }
            }
            return vo;
        }).collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void approveTask(Long userId, BpmTaskApproveReqVO reqVO) {
        // 校验任务存在
        Task task = checkTask(userId, reqVO.getId());
        // 校验流程实例存在
        ProcessInstance instance = processInstanceExtService.getProcessInstance(task.getProcessInstanceId());
        if (BeanUtil.isEmpty(instance)) {
            throw new WebApiException(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 完成任务，审批通过
        taskService.complete(task.getId(), instance.getProcessVariables());
        // 更新任务拓展表为通过
        BpmTaskExtDO ext = new BpmTaskExtDO();
        ext.setTaskId(task.getId());
        ext.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult());
        ext.setReason(reqVO.getReason());
        taskExtMapper.update(ext, new LambdaQueryWrapperBase<BpmTaskExtDO>().eq(BpmTaskExtDO::getTaskId, task.getId()));
        // 判断任务是否为或签，或签时删除其余不用审批的任务
    }

    @Override
    public void rejectTask(Long userId, BpmTaskRejectReqVO reqVO) {
        Task task = checkTask(userId, reqVO.getId());
        // 校验流程实例存在
        ProcessInstance processInstance = processInstanceExtService.getProcessInstance(task.getProcessInstanceId());
        if (BeanUtil.isEmpty(processInstance)) {
            throw new WebApiException(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 更新流程实例为不通过
        processInstanceExtService.updateProcessInstanceExtReject(processInstance.getProcessInstanceId(), reqVO.getReason());
        // 更新任务拓展表为不通过

        BpmTaskExtDO ext = new BpmTaskExtDO();
        ext.setTaskId(task.getId());
        ext.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
        ext.setReason(reqVO.getReason());
        taskExtMapper.update(ext, new LambdaQueryWrapperBase<BpmTaskExtDO>()
                .eq(BpmTaskExtDO::getTaskId, task.getId()));
    }

    @Override
    public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) {
        // 校验任务存在
        Task task = checkTask(userId, reqVO.getId());
        // 更新负责人
        updateTaskAssignee(task.getId(), reqVO.getAssigneeUserId());
    }

    @Override
    public void updateTaskAssignee(String id, Long userId) {
        taskService.setAssignee(id, String.valueOf(userId));
    }


    @Override
    public IPage<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
        // 查询待办任务
        TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(userId.toString())
                // 创建时间倒序
                .orderByTaskCreateTime().desc();
        if (StrUtil.isNotBlank(pageVO.getName())) {
            taskQuery.taskNameLike("%" + pageVO.getName() + "%");
        }
        if (pageVO.getBeginCreateTime() != null) {
            taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
        }
        if (pageVO.getEndCreateTime() != null) {
            taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
        }
        // 执行查询
        List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getSize());
        if (CollUtil.isEmpty(tasks)) {
            return new Page<>();
        }
        // 获得 ProcessInstance Map
        Set<String> processInstanceIds = tasks.stream().map(Task::getProcessInstanceId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<String, ProcessInstance> processInstanceMap =
                processInstanceExtService.getProcessInstanceMap(processInstanceIds);
        // 获得 User Map
        Set<Long> startUserIds = processInstanceMap.values().stream().map(instance -> Long.valueOf(instance.getStartUserId()))
                .collect(Collectors.toSet());
        Map<Long, UserInfoDTO> userMap = adminUserApi.getUserMap(startUserIds);

        List<BpmTaskTodoPageItemRespVO> voList = tasks.stream().map(task -> {
            BpmTaskTodoPageItemRespVO vo = new BpmTaskTodoPageItemRespVO();
            vo.setId(task.getId());
            vo.setName(task.getName());
            vo.setClaimTime(task.getClaimTime());
            vo.setCreateTime(task.getCreateTime());
            int stateCode = task.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode();
            vo.setSuspensionState(stateCode);
            ProcessInstance instance = processInstanceMap.get(task.getProcessInstanceId());
            if (BeanUtil.isNotEmpty(instance)) {
                UserInfoDTO userInfoDTO = userMap.get(NumberUtils.parseUpperCaseLong(instance.getStartUserId()));
                Map<String, Object> variables = taskService.getVariables(task.getId());
                BpmTaskTodoPageItemRespVO.ProcessInstance processInstance = new BpmTaskTodoPageItemRespVO.ProcessInstance();
                processInstance.setId(instance.getId());
                processInstance.setName(instance.getName());
                processInstance.setStartUserNickname(userInfoDTO.getNickName());
                processInstance.setStartUserId(userInfoDTO.getId());
                processInstance.setProcessDefinitionId(instance.getProcessDefinitionId());
                vo.setProcessInstance(processInstance);
            }
            return vo;
        }).collect(Collectors.toList());
        long count = taskQuery.count();
        Page<BpmTaskTodoPageItemRespVO> page = new Page<>();
        page.setRecords(voList);
        page.setTotal(count);
        page.setSize(pageVO.getSize());
        page.setCurrent(pageVO.getCurrent());
        page.setPages(count / pageVO.getSize() + 1);
        return page;
    }

    @Override
    public IPage<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO) {
        // 查询已办任务
        HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
                // 已完成
                .finished()
                // 分配给自己
                .taskAssignee(String.valueOf(userId))
                // 审批时间倒序
                .orderByHistoricTaskInstanceEndTime()
                .desc();
        if (StrUtil.isNotBlank(pageReqVO.getName())) {
            taskQuery.taskNameLike("%" + pageReqVO.getName() + "%");
        }
        if (pageReqVO.getBeginCreateTime() != null) {
            taskQuery.taskCreatedAfter(pageReqVO.getBeginCreateTime());
        }
        if (pageReqVO.getEndCreateTime() != null) {
            taskQuery.taskCreatedBefore(pageReqVO.getEndCreateTime());
        }
        List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getSize());

        if (CollUtil.isEmpty(tasks)) {
            return new Page<>();
        }
        Set<String> taskIds = tasks.stream()
                .map(HistoricTaskInstance::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        List<BpmTaskExtDO> taskExtDOList = taskExtMapper.selectList(new LambdaQueryWrapperBase<BpmTaskExtDO>()
                .in(BpmTaskExtDO::getTaskId, taskIds));

        // 获得 TaskExtDO Map
        Map<String, BpmTaskExtDO> taskExtDOMap = new HashMap<>();
        if (CollUtil.isNotEmpty(taskExtDOList)) {
            taskExtDOMap.putAll(taskExtDOList.stream()
                    .collect(Collectors.toMap(BpmTaskExtDO::getTaskId, Function.identity(), (key1, key2) -> key1)));
        }

        // 获得 ProcessInstance Map
        Set<String> instanceIds = tasks.stream()
                .map(HistoricTaskInstance::getProcessInstanceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<String, HistoricProcessInstance> historicProcessInstanceMap = processInstanceExtService.getHistoricProcessInstanceMap(instanceIds);
        // 获得 User Map
        Set<Long> userIds = historicProcessInstanceMap.values()
                .stream().map(instance -> Long.valueOf(instance.getStartUserId()))
                .filter(ObjectUtil::isNotEmpty)
                .collect(Collectors.toSet());
        Map<Long, UserInfoDTO> userMap = adminUserApi.getUserMap(userIds);

        List<BpmTaskDonePageItemRespVO> list = tasks.stream().filter(Objects::nonNull).map(task -> {
            BpmTaskDonePageItemRespVO vo = BeanUtil.copyProperties(task, BpmTaskDonePageItemRespVO.class);
            BpmTaskExtDO ext = taskExtDOMap.get(task.getId());
            BeanUtil.copyProperties(ext, vo, "id");
            HistoricProcessInstance processInstance = historicProcessInstanceMap.get(task.getProcessInstanceId());
            if (BeanUtil.isNotEmpty(processInstance)) {
                UserInfoDTO startUser = userMap.get(NumberUtils.parseUpperCaseLong(processInstance.getStartUserId()));
                BpmTaskTodoPageItemRespVO.ProcessInstance instance = new BpmTaskTodoPageItemRespVO.ProcessInstance();
                BeanUtil.copyProperties(processInstance, instance);
                instance.setStartUserNickname(startUser.getNickName());
                vo.setProcessInstance(instance);
            }
            return vo;
        }).collect(Collectors.toList());
        long count = taskQuery.count();
        Page<BpmTaskDonePageItemRespVO> page = new Page<>();
        page.setRecords(list);
        page.setTotal(count);
        page.setSize(pageReqVO.getSize());
        page.setCurrent(pageReqVO.getCurrent());
        page.setPages(count / pageReqVO.getSize() + 1);
        return page;
    }

    /**
     * 校验任务是否存在， 并且是否是分配给自己的任务
     *
     * @param userId 用户 id
     * @param taskId task id
     */
    private Task checkTask(Long userId, String taskId) {
        Task task = getTask(taskId);
        if (task == null) {
            throw new WebApiException(ErrorCodeConstants.TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        if (!Objects.equals(userId, NumberUtils.parseUpperCaseLong(task.getAssignee()))) {
            throw new WebApiException(ErrorCodeConstants.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
        }
        return task;
    }

    private Task getTask(String id) {
        return taskService.createTaskQuery().taskId(id).singleResult();
    }

    public HistoricProcessInstance getHistoricProcessInstance(String instanceId) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
    }

}
